package io.gitee.devaper.starlight.common.config

import io.gitee.devaper.starlight.*
import org.slf4j.*
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.boot.convert.DurationUnit
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import java.time.Duration
import java.time.temporal.*
import java.util.concurrent.*
import java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy

/**
 * 默认线程池的bean名称
 */
const val DEFAULT_EXECUTOR_SERVICE_BEAN_NAME = "defaultExecutorService"

/**
 * Application事件发布器线程池的bean名称
 */
const val APPLICATION_EVENT_MULTICASTER_EXECUTOR_SERVICE_BEAN_NAME = "applicationMulticasterTaskExecutor"


/**
 * 线程池配置类
 *
 * 所有的线程池声明在此处, 便于管理每个线程的资源分配
 */
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(ThreadPoolsProperties::class)
open class ThreadPoolConfiguration(private val properties: ThreadPoolsProperties) {

    /**
     * 默认的线程池, 没有指定类别的线程池时可以投递到该线程池
     *
     * 如果任务数量比较大, 尽量考虑单独创建一个线程池, 并使用不同的队列、策略来处理任务
     */
    @Bean(DEFAULT_EXECUTOR_SERVICE_BEAN_NAME)
    open fun defaultExecutorService(): ExecutorService {
        return with(properties.default) {
            ThreadPoolExecutor(
                coreSize,
                maxSize,
                keepAliveTime.toSeconds(),
                TimeUnit.SECONDS,
                LinkedBlockingQueue(queueCapacity),
                Executors.defaultThreadFactory(),
                CallerRunsPolicy()
            )
        }
    }

    /**
     * Spring容器事件相关处理逻辑使用的线程池
     */
    @Bean(APPLICATION_EVENT_MULTICASTER_EXECUTOR_SERVICE_BEAN_NAME)
    open fun applicationEventMulticasterExecutorService(): ExecutorService {

        val exceptionHandler = Thread.UncaughtExceptionHandler { _, e ->
            log.error(e.message, e)
        }

        return with(properties.applicationEventMulticaster) {
            ThreadPoolExecutor(
                coreSize,
                maxSize,
                keepAliveTime.toSeconds(),
                TimeUnit.SECONDS,
                LinkedBlockingQueue(queueCapacity),
                { r ->
                    Thread(r).apply {
                        name = "applicationEventMulticaster-${r.hashCode()}"
                        uncaughtExceptionHandler = exceptionHandler
                    }
                },
                CallerRunsPolicy()
            )
        }
    }

    companion object {
        val log = LoggerFactory.getLogger(ThreadPoolConfiguration::class.java)
    }

}

@ConfigurationProperties(prefix = "${CONFIG_PREFIX}.thread-pool")
data class ThreadPoolsProperties(
    val default: ThreadPoolProperties = ThreadPoolProperties(),
    val applicationEventMulticaster: ThreadPoolProperties = ThreadPoolProperties()
)

data class ThreadPoolProperties(
    var coreSize: Int = 0,
    var maxSize: Int = 0,
    @field:DurationUnit(ChronoUnit.MILLIS)
    var keepAliveTime: Duration = Duration.ZERO,
    var queueCapacity: Int = 0
)